package com.esdk.sql;

import com.esdk.esdk;
import com.esdk.exception.SdkRuntimeException;
import com.esdk.sql.orm.IRow;
import com.esdk.sql.orm.IRowSet;
import com.esdk.sql.orm.ParentRow;
import com.esdk.utils.CharAppender;
import com.esdk.utils.TimeMeter;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @author ffychina@163.com
 * @since 2024-06-22
 * 通过多条sql一次性执行的方式批量update
 * */
public class BatchMultiUpdate implements ISQL, IShowSql{
  protected String tableName;
  protected List<String> updateSQLs=new ArrayList<>();
  private Connection _conn;
  private boolean isShowSql=true;
  private List<Object> rowList=new ArrayList<>();
  private int performCount=0;
  private boolean ignoreRowChanged=false;
  private long executeConcumingTime;

  /**
   * 默认ignoreRowChanged=false，即需要判断row对象是否被修改过
   * 注意1：要先在jdbc.url增加属性：allowMultiQueries=true
   * 注意2：自增字段不能为Null
   */
  public BatchMultiUpdate(String tablename,Connection conn){
    this.tableName=tablename;
    setConnection(conn);
  }

  public BatchMultiUpdate(String tablename,Connection conn,boolean isForceReplace){
    this.tableName=tablename;
    setConnection(conn);
    this.ignoreRowChanged=isForceReplace;
  }

  /**添加序号注释是为了方便在打印时直接看到一共有多少条sql，跟省略sql区分得更明显示，但不要也行*/
  @Override
  public String getSQL(){
    parse();
		String result="";
		for(int i=0;i<updateSQLs.size();i++){
			result+="/*"+(i+1)+":*/"+updateSQLs.get(i);
		}
    return result;
  }

  public String getEllipseSql(){
    StringBuilder result=new StringBuilder();
    if(!rowList.isEmpty()){
      result.append(esdk.array.toStr(updateSQLs.subList(0,2).toArray()," "));
    }
    result.append("...");
    return result.toString();
  }

  public String getTableName(){
    return tableName;
  }

  @Override
  public void clear(){
    tableName=null;
    rowList.clear();
  }

  public void clearValues(){
    rowList.clear();
  }

  public int size(){
    return rowList.size();
  }

  @Override public BatchMultiUpdate showSql(boolean isShowSql){
    this.isShowSql=isShowSql;
    return this;
  }

  @Override public boolean showSql(){
    return this.isShowSql;
  }

  public BatchMultiUpdate add(IRow row){
    if(ignoreRowChanged||row.isChanged()){
      rowList.add(row);
    }
    return this;
  }

  public BatchMultiUpdate add(ParentRow row){
    if(ignoreRowChanged||row.isChanged()){
      if(row.isAutoIncrement()&&row.getPKID()==null){
        throw new SdkRuntimeException("主键值不能为空！");
      }
      rowList.add(row);
    }
    return this;
  }

  public BatchMultiUpdate add(Update update){
    rowList.add(update);
    return this;
  }
  public <PR extends ParentRow> BatchMultiUpdate addAll(Collection<PR> collection){
    for(ParentRow row: collection){
      add(row);
    }
    return this;
  }

  public <R extends IRow> BatchMultiUpdate addAll(IRowSet<R> rs){
    for(IRow row: rs){
      add(row);
    }
    return this;
  }

  public BatchMultiUpdate addAll(List<Update> list){
    for(Update update: list){
      add(update);
    }
    return this;
  }

  @Override
  public void parse(){
    updateSQLs.clear();
    for(Object item: this.rowList){
        if(item instanceof ParentRow){
          ParentRow parentRow=(ParentRow)item;
          if(ignoreRowChanged || parentRow.isChanged()){
            parentRow.fillCommonFields();
            if(!tableName.equals(parentRow.getTableName())){
              parentRow.setTableName(tableName);
            }
            updateSQLs.add(parentRow.showSQL()+";");
          }
        }else if(item instanceof IRow){
          IRow row=(IRow)item;
          if(ignoreRowChanged || row.isChanged()){
            String primaryKeyName=row.getColumns()[0];
            Map<String,Object> changed=row.getChanged();
            CharAppender sets=new CharAppender(',');
            for(Map.Entry<String,Object> entry: changed.entrySet()){
              if(!entry.getKey().equals(primaryKeyName)){
                sets.add(entry.getKey()+"="+SQLAssistant.getStmtSqlValue(entry.getValue()));
              }
            }
            String updateSql="UPDATE "+tableName+" SET "+sets.toString()+" WHERE "+primaryKeyName+"="+SQLAssistant.getStmtSqlValue(row.get(primaryKeyName));
            updateSQLs.add(updateSql+";");
          }
        }else if(item instanceof Update){
          updateSQLs.add(item+";");
        }
    }
  }

  /**
   * 自动分段执行
   */
  public int perform(int limit,boolean autoCleanValues) throws SQLException{
    List all=rowList;
    int count=size();
    for(int i=0, n=esdk.math.ceil(count,limit);i<n;i++){
      rowList=all.subList(i*limit,esdk.math.min((i+1)*limit,all.size()));
      perform(false);
    }
    if(autoCleanValues)
      clearValues();
    else
      rowList=all;
    performCount=count;
    return performCount;
  }

  /**
   * 如果达到或超出的数量则执行
   */
  public int performIfOverload(int limit) throws SQLException{
    if(size()>=limit){
      int before=performCount;
      int count=perform(true);
      performCount=before+count;
      return count;
    }else
      return 0;
  }

  /**
   * 执行批处理sql并清空values值
   */
  public int perform(boolean autoCleanValues) throws SQLException{
    performCount=_perform()?size():0;
    if(performCount==size()&&autoCleanValues)
      clearValues();
    return performCount;
  }

  @Override
  public int perform() throws SQLException{
    performCount=perform(true);
    return performCount;
  }

  /**
   * 获取影响的记录数
   */
  public int getPerformCount(){
    return performCount;
  }

  private boolean _perform() throws SQLException{
    if(_conn==null)
      throw new SQLException("Connection is null,please invoke setConnectcion before perform");
    if(size()==0)
      return false;
    parse();
    try{
			TimeMeter tm=TimeMeter.startNew();
      int updateCount=esdk.sql.executeBatchUpdate(_conn,updateSQLs,updateSQLs.size(),true,_conn.getAutoCommit());
      this.executeConcumingTime=tm.getElapse();
			if(isShowSql)
				SQLAssistant.showSql(this);
    }catch(SQLException e){
      esdk.error("SQL执行出现异常，错误提示："+e.toString()+"。执行的SQL为："+esdk.str.ellipsis(this.getSQL(),10000));
      throw e;
    }
    //return flag;; //待分析，目前测试在Mysql环境下，flag永远都是返回false.
    return true;
  }

  @Override
  public Connection getConnection(){
    return _conn;
  }

  @Override
  public void setConnection(Connection conn){
    this._conn=conn;
    SQLAssistant.setDefaultDialect(this._conn);
  }

  @Override public long getExecuteConsuming(){
    return executeConcumingTime;
  }
}
